package com.bolt.support.spring.jpa.jpql;

import com.bolt.common.utils.StrUtil;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory;
import org.hibernate.hql.spi.NamedParameterInformation;
import org.hibernate.hql.spi.ParameterTranslations;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.hql.spi.QueryTranslatorFactory;

import javax.persistence.EntityManager;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HqlTranslator {

    private final SessionImplementor hibernateSession;
    private final QueryTranslatorFactory translatorFactory;

    public HqlTranslator(EntityManager entityManager) {

        hibernateSession = entityManager.unwrap(SessionImplementor.class);
        translatorFactory = new ASTQueryTranslatorFactory();
    }

    public String translate(String hql) {
        if (hql == null || hql.trim().length() == 0) {
            return "";
        }
        try {
            final QueryTranslator translator
                    = translatorFactory.createQueryTranslator(
                    hql, hql, Collections.EMPTY_MAP,
                    hibernateSession.getSessionFactory(), null);
            translator.compile(Collections.EMPTY_MAP, false);
            final String sqlString = translator.getSQLString();
            Map<Integer, Integer> paramLocation = matchParamLocation(sqlString);
            StringBuffer sqlBuffer = new StringBuffer(sqlString);
            ParameterTranslations parameterTranslations = translator.getParameterTranslations();
            int pos = 0;
            Map<String, NamedParameterInformation> parameterInformationMap = parameterTranslations.getNamedParameterInformationMap();

            Set<String> keys = parameterInformationMap.keySet();
            for (String paramName : keys) {
                NamedParameterInformation namedParameterInformation = parameterInformationMap.get(paramName);
                int[] locations = namedParameterInformation.getSourceLocations();
                for (int loc : locations) {
                    int index = paramLocation.get(loc);
                    if (index != -1) {
                        String newParamName = ":" + paramName;
                        int newIndex = index + pos;
                        sqlBuffer.replace(newIndex, newIndex + 1, newParamName);
                        pos = pos + newParamName.length() - 1;
                    }
                }
            }

            String sqlWithNamedParams = StrUtil.removeAll(sqlBuffer.toString(), "?");
            return sqlWithNamedParams;
        } catch (QueryException | MappingException t) {
            return ExceptionUtils.getStackTrace(t);
        }
    }

    private Map<Integer, Integer> matchParamLocation(String sql) {
        Pattern pattern = Pattern.compile("(\\?+)");
        Map<Integer, Integer> paramLocation = new TreeMap();
        Matcher m = pattern.matcher(sql);
        int loc = 0;
        while (m.find()) {
            paramLocation.put(loc, m.start());
            loc++;
        }
        return paramLocation;
    }

}
